#include "xmppcore/connector.h"
#include "xmppcore/tlscallback.h"
#include "utils/constants.h"

#include <QtNetwork/QTcpSocket>
#include <QtCore/QTextCodec>

namespace yoohoo_xmppcore {

const quint16 Connector::DEFAULT_MAX_PACKET_LENGTH = 1024 * 32; // 32k bytes

Connector::Connector(Server& server, TlsCallback* tlsCallback, QObject* parent) : QObject(parent) {
	_tlsCallback = tlsCallback;
	_socket = 0;
	_textStream = 0;
	_socketState = QAbstractSocket::UnconnectedState;
	_server = new Server(server.host(), server.port());
	_maxPacketLength = DEFAULT_MAX_PACKET_LENGTH;
	_tls = 0;
	_tlsState = TlsStateNone;
}

Connector::~Connector() {
	disconnect();

	delete _server;
	delete _tlsCallback;
}

void Connector::write(QString message) {
	QMutexLocker locker(&_socketWriteLock);
	if (_socketState != QAbstractSocket::ConnectedState) {
		qDebug("Not connected. Can't write message");
		return;
	}

	if (_tlsState != TlsStateEstablished && _textStream != 0) {
		*_textStream << message;
		_textStream->flush();
	} else {
		if (_tls != 0) {
			_tls->write(message.toUtf8());
		}
	}
}

void Connector::connectToHost() {
	QMutexLocker locker(&_socketWriteLock);
	if (_socket != 0 && _socket->isOpen())
		disconnect();

	if (_socket == 0) {
		_socket = new QTcpSocket();
		if (!_textStream) {
			delete _textStream;
			_textStream = 0;
		}
		_textStream = new QTextStream(_socket);
		_textStream->setCodec(QTextCodec::codecForName("UTF-8"));

		connect(_socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
				this, SLOT(changeSocketState(QAbstractSocket::SocketState)));
		connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)),
				this, SIGNAL(socketError(QAbstractSocket::SocketError)));
		connect(_socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead()));
	}

	if (_server == 0) {
		qWarning("Connector::connectToHost: No available server");

		return;
	}

	qDebug("Try to connect to host %s:%d", qPrintable(_server->host()), _server->port());
	_socket->connectToHost(_server->host(), _server->port());
}

void Connector::socketReadyRead() {
	if (_tlsState != TlsStateNone) {
		_tls->writeIncoming(_socket->readAll());
	} else {
		QString dataReceived;
		while (!_textStream->atEnd()) {
			dataReceived += _textStream->read(4098);
		}

		parseAndProcessReceivedData(dataReceived);
	}
}

void Connector::parseAndProcessReceivedData(QString& dataReceived) {
	if (dataReceived.size() > _maxPacketLength) {
		// received data is too large, abandon it
		int length = dataReceived.size();
		dataReceived.clear();

		emit abandonData(length);
	}

	QList<QString> xmls = _parser.parse(dataReceived);
	processXmls(xmls);
}

void Connector::processXmls(QList<QString> xmls) {
	foreach (QString xml, xmls) {
		if (_socketState != QAbstractSocket::UnconnectedState)
			emit received(xml);
	}
}

void Connector::changeSocketState(QAbstractSocket::SocketState socketState) {
	_socketState = QAbstractSocket::ConnectedState;
	emit socketStateChanged(_socketState);

	switch (_socketState) {
	case QAbstractSocket::ConnectedState:
		qDebug("Connector::changeSocketState: Connected");
		emit socketReady();
		break;
	case QAbstractSocket::UnconnectedState:
		qDebug("Connector::changeSocketState: Unconnected");
		break;
	case QAbstractSocket::HostLookupState:
		qDebug("Connector::changeSocketState: Host lookup");
		break;
	case QAbstractSocket::ConnectingState:
		qDebug("Connector::changeSocketState: Connecting");
		break;
	case QAbstractSocket::ClosingState:
		qDebug("Connector::changeSocketState: Closing");
		break;
	default:
		qDebug("Connector::changeSocketState: Unknown socket state");
		break;
	}
}

void Connector::disconnectFromHost() {
	QMutexLocker locker(&_socketWriteLock);
	if (_socket == 0)
		return;

	if (_socketState == QAbstractSocket::HostLookupState || _socketState == QAbstractSocket::ConnectingState) {
		qDebug("Connector::disconnectFromHost: Abort connector");
		_socket->abort();
	} else if (_socketState == QAbstractSocket::ConnectedState || _tlsState == TlsStateNegotiating) {
		qDebug("Connector::disconnectFromHost: Disconnect from host");
		_socket->disconnectFromHost();
	}

	while (_socketState != QAbstractSocket::UnconnectedState) {
		qDebug("Wait socket to quit");
		_socket->waitForDisconnected(1000);
	}

	delete _socket;
	delete _textStream;
	_textStream = 0;

	if (_tls != 0 && _tls->isClosable() && _tlsState == TlsStateEstablished) {
		_tls->close();
		delete _tls;
	}

	qDebug("Connector::disconnectFromHost: Release connector");

	_socketState = QAbstractSocket::UnconnectedState;
}

void Connector::setTlsCallback(TlsCallback* tlsCallback) {
	_tlsCallback = tlsCallback;
}

const Server* const Connector::server() const {
	return _server;
}

void Connector::startTls() {
	qDebug("Connector::startTls: Start tls");

	if (!QCA::isSupported("tls")) {
		return;
	}

	if (_tls == 0) {
		_tls = new TLS;
		connect(_tls, SIGNAL(closed()), this, SLOT(tlsClosed()));
		connect(_tls, SIGNAL(error()), this, SLOT(tlsError()));
		connect(_tls, SIGNAL(certificateRequested()), this, SLOT(tlsCertificateRequested()));
		connect(_tls, SIGNAL(handshaken()), this, SLOT(tlsHandshaken()));
		connect(_tls, SIGNAL(peerCertificateAvailable()), this, SLOT(tlsPeerCertificateAvailable()));
		connect(_tls, SIGNAL(readyRead()), this, SLOT(tlsReadyRead()));
		connect(_tls, SIGNAL(readyReadOutgoing()), this, SLOT(tlsReadyReadOutgoing()));

		if (!haveSystemStore()) {
			qDebug("No system store.");

			CertificateCollection trustedCertificates;
			_tls->setTrustedCertificates(trustedCertificates);
		} else {
			_tls->setTrustedCertificates(systemStore());
		}
	}

	_tlsState = TlsStateNegotiating;
	_tls->startClient();
}

void Connector::tlsClosed() {
	qDebug("Connector::tlsClosed: TLS closed");
	_tlsState = TlsStateNone;
}

void Connector::tlsError() {
	_tlsState = TlsStateNone;

	int x = _tls->errorCode();
	qDebug("Connector::tlsError: error code[%d]", _tls->errorCode());
	emit tlsError(x);
	
	disconnect();
}

void Connector::tlsCertificateRequested() {
	QList<CertificateInfoOrdered> issuerList = _tls->issuerList();
	if(!issuerList.isEmpty()) {
		qDebug("Connector::tlsCertificateRequested: Allowed issuers:\n");
		foreach(CertificateInfoOrdered certificateInfo, issuerList)
			qDebug("Connector::tlsCertificateRequested: %s", qPrintable(certificateInfo.toString()));
	}
}
	
void Connector::tlsHandshaken() {
	qDebug("Connector::tlsHandshaken: Handshaken");

	bool certificateAccepted = false;

	if (_tlsCallback == 0) {
		certificateAccepted = true;
	} else {
		TLS::IdentityResult identityResult = _tls->peerIdentityResult();
		Validity validity = _tls->peerCertificateValidity();
		CertificateChain certificateChain = _tls->peerCertificateChain();

		PeerCertificateInfo certInfo(&identityResult, &validity, &certificateChain);

		QMetaObject::invokeMethod(_tlsCallback, "acceptCertificate",
			Qt::BlockingQueuedConnection, Q_ARG(PeerCertificateInfo*, &certInfo),
			Q_ARG(bool*, &certificateAccepted));
	}

	if (certificateAccepted) {
		qDebug("Connector::_tlsPeerCertificateAvailable: Certificate accepted");
		_tls->continueAfterStep();
		_tlsState = TlsStateEstablished;
		emit tlsReady();
	} else {
		qDebug("Connector::tlsPeerCertificateAvailable: Certificate rejected");
		_tlsState = TlsStateNone;

		disconnect();
	}
}

void Connector::tlsPeerCertificateAvailable() {
	qDebug("Connector::tlsPeerCertificateAvailable: TLS peer certificate is available");
}

void Connector::tlsReadyRead() {
	QString dataReceived(QString::fromUtf8(_tls->read()));
	parseAndProcessReceivedData(dataReceived);
}

void Connector::tlsReadyReadOutgoing() {
	QMutexLocker locker(&_socketWriteLock);
	if (_socket != 0) {
		_socket->write(_tls->readOutgoing());
	}
}

}
